from django.http import HttpResponseRedirect
from django.http.response import JsonResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
import json
from lib.utils import is_chinese
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
import zipfile

from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import TemplateView, CreateView, DetailView

from .. import forms, models
from .lib import template_path
from media.models import File
import os
from pypinyin import Style, lazy_pinyin
from collections import defaultdict
from media.lib.utils import store_permanent
import logging
from django_site import settings
from audit.models import AuditManagement, AUDIT_STATUS

logger = logging.getLogger(__name__)
User = get_user_model()

class UserManagementCommonView(
    LoginRequiredMixin,
    PermissionRequiredMixin,
    TemplateView
):
    template_name = template_path("management/user_common.html")
    permission_required = "user.can_manage_users"

class ChangeUserPermissionView(
    LoginRequiredMixin,
    PermissionRequiredMixin,
    TemplateView
):
    template_name = template_path("management/user_common.html")
    permission_required = "user.can_manage_users"

class InviteCodeListView(
    LoginRequiredMixin,
    PermissionRequiredMixin,
    CreateView
):
    form_class = forms.CreateInviteCodeForm
    template_name = template_path("management/registry.html")
    permission_required = "user.can_manage_users"
    success_url = reverse_lazy("user:management/invite-code")

    def form_valid(self, form):
        user = self.request.user
        obj = form.save(commit=False)
        obj.creater = user
        obj.save()
        self.object = obj
        return HttpResponseRedirect(self.get_success_url())
        

class UserPublicProfileView(
    LoginRequiredMixin,
    DetailView
):
    model = models.UserProfile
    template_name = template_path('profile.html')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        object = self.object
        if not object.is_researcher:
            return context
        user = self.request.user
        researcher = object.researcher_profile
        resumes = File.objects.get_for_object(researcher, "resume")
        resumes = map(lambda r: {'can_access': r.can_access(user), 'resume': r}, resumes)
        context['resumes'] = resumes
        return context
        

# TODO set cache
class ResearcherListView(
    LoginRequiredMixin,
    TemplateView
):
    template_name = template_path("researchers.html")
    success_url = reverse_lazy("user:researchers")

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        researchers_by_letter = {}
        available_letters = set()

        user_researchers = models.UserProfile.objects.filter(is_researcher=True)
        for user in user_researchers:
            full_name = user.full_name.strip()
            full_name = full_name if full_name else user.username
            first_letter = lazy_pinyin(full_name, style=Style.FIRST_LETTER)[0][0]
            if first_letter not in researchers_by_letter:
                researchers_by_letter[first_letter] = defaultdict(list)
            d = researchers_by_letter[first_letter]
            d[full_name].append(user)

        for first_letter, d in researchers_by_letter.items():
            available_letters.add(first_letter)
            m = dict()
            for full_name, researchers in d.items():
                if len(researchers) == 1:
                    m[full_name] = researchers[0].id
                else:
                    full_name_backup = full_name
                    for researcher in researchers:
                        full_name = f"{full_name_backup}({researcher.username})"
                        m[full_name] = researcher.id
            researchers_by_letter[first_letter] = m

        # sort letter alphabetically
        researchers_by_letter = {k: v for k, v in sorted(researchers_by_letter.items())}
            
        context.update({
            'researchers_by_letter': researchers_by_letter,
            'available_letters': available_letters
        })

        return context

class ResearchersResumesBatchUploadHTMXView(
    LoginRequiredMixin,
    PermissionRequiredMixin,
    TemplateView
):
    permission_required = "user.can_manage_users"
    htmx_template_name = template_path("management/batch-upload-researchers-resumes.html")
    
    def get_template_names(self): # pyright:ignore [ reportIncompatibleMethodOverride ]
        if self.request.htmx:
            return [self.htmx_template_name]
        raise Exception("This view is meant to be only used in HTMX context!")

    def post(self, request, *args, **kwargs):
        try:
            files_info = json.loads(request.body)
            
            # Fetch groups once
            external_group = Group.objects.get(name='external_personal')
            internal_group = Group.objects.get(name='internal_staff')

            processed_files = set()
            
            # Cache for tracking created users
            raw_researcher_name_to_username_map = {}
            user_cache = {}
            
            # Pre-fetch existing users to reduce database queries
            assignee_usernames = [
                info['assignee'] for info in files_info
                if info['assignee'] != "####"
            ]
            
            if assignee_usernames:
                for user in User.objects.filter(username__in=assignee_usernames):
                    user_cache[user.username] = user

            for info in files_info:
                temp_file_path = settings.MEDIA_TMP_UPLOAD_ROOT / info['serverID']
                dirname, top_filename = os.path.split(temp_file_path)
                _, extension = os.path.splitext(temp_file_path)
                is_zip = extension == ".zip"
                if temp_file_path not in processed_files:
                    if is_zip: # unzip zip files
                        target_dir = dirname + "/extracted"
                        try: 
                            with zipfile.ZipFile(temp_file_path, 'r') as zip_ref:
                                file_list = zip_ref.namelist()
                                for file in file_list:
                                    normpath = os.path.normpath(file)
                                    _, file_ext = os.path.splitext(file)
                                    is_safe_path = not normpath.startswith('..')
                                    is_valid_file_type = file_ext in (
                                        ".pdf", ".doc", ".docx",
                                        ".png", ".jpg", ".jpeg"
                                    )
                                    if not (is_safe_path and is_valid_file_type):
                                        return JsonResponse(
                                            {"error": f"File {file} is invalid.",},
                                            status=400
                                        )
                                    zip_ref.extract(file, target_dir)
                        except zipfile.BadZipFile:
                            return JsonResponse(
                                {"error": f"The zip file '{top_filename}' is corrupted."},
                                status=400
                            )

                    processed_files.add(temp_file_path)

                # Case 1: Create new researcher
                if info['assignee'] == "####":
                    raw_researcher_name = info['newResearcherName']
                    if not raw_researcher_name:
                        return JsonResponse({"error": "You must specify a new researcher name"}, status=400)
                    
                    # Check if already created in this batch
                    username = raw_researcher_name_to_username_map.get(raw_researcher_name)
                    if username:
                        user = user_cache[username]
                    else:
                        # Create new researcher
                        researcher_name = raw_researcher_name.split("+")[0]
                        
                        # Parse name
                        if is_chinese(researcher_name):
                            last_name = researcher_name[0]
                            first_name = researcher_name[1:]
                        else:
                            try:
                                first_name, last_name = researcher_name.split(" ", 1)
                            except ValueError:
                                return JsonResponse({"error": f"Invalid name format: {researcher_name}. Expected 'firstname lastname'."}, status=400)

                        # Generate username with collision handling
                        base_username = "".join(lazy_pinyin(researcher_name, style=Style.NORMAL))
                        username = base_username
                        index = 0
                        
                        # Find an available username more efficiently
                        while username in user_cache or User.objects.filter(username=username).exists():
                            index += 1
                            username = f"{base_username}-{index}"
                            
                        # Create the user
                        user = User.objects.create_user(
                            username, "", "by6pms",
                            first_name=first_name,
                            last_name=last_name
                        )
                        
                        am = AuditManagement.objects.get_for_object(user)
                        am.status = AUDIT_STATUS.APPROVED
                        am.save()
                        
                        user.groups.remove(external_group)
                        user.groups.add(internal_group)
                        user.is_researcher = True
                        user.save()

                        # Update caches
                        raw_researcher_name_to_username_map[raw_researcher_name] = username
                        user_cache[username] = user
                        
                # Case 2: Assign to existing user
                else:
                    username = info['assignee']
                    
                    # Get user from cache or error
                    user = user_cache.get(username)
                    if not user:
                        return JsonResponse({"error": f"Assignee with username {username} doesn't exist!"}, status=400)

                    # Ensure user is marked as researcher
                    if not user.is_researcher:
                        user.is_researcher = True
                        user.save()
                        
                # Store resumes
                resume_file_path = temp_file_path
                
                if is_zip:
                    resume_file_path = dirname + "/extracted/" + info['filename']
                    
                researcher_profile = user.researcher_profile
                store_permanent(resume_file_path, researcher_profile, "resume", user)
                
            return JsonResponse({"error": "success"})
        
        except json.JSONDecodeError:
            return JsonResponse({"error": "Invalid JSON data"}, status=400)
        except Exception as e:
            # Log the exception for debugging
            logger.error(f"Error in batch upload: {str(e)}")
            return JsonResponse({"error": "An unexpected error occurred"}, status=500)




